package uno.rocc.framework.security.config;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import uno.rocc.common.constant.RedisConstant;
import uno.rocc.common.constant.SecurityConstant;
import uno.rocc.common.exception.ServiceException;
import uno.rocc.common.util.Result;
import uno.rocc.framework.security.domain.RoccUnoUserDetails;
import uno.rocc.framework.security.service.impl.RoccUnoUserDetailsServiceImpl;
import uno.rocc.framework.security.util.JwtUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@Slf4j
public class SecurityConfig {
    private final RoccUnoUserDetailsServiceImpl userDetailsService;
    private final JwtUtil jwtUtil;
    private final RedisTemplate<String, Object> redisTemplate;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().userDetailsService(userDetailsService)
                .exceptionHandling()
                .authenticationEntryPoint(this::handleAuthenticationEntryPoint)
                .and()
                .addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                //允许访问
                .antMatchers("/register", "/login", "/logout", "/captcha","/doc.html", "/webjars/**", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
                //所有请求都需要认证
                .anyRequest().authenticated();

        return http.build();
    }

    //验证token
    public OncePerRequestFilter tokenFilter() {
        return new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                String authorization = request.getHeader("Authorization");
                if (authorization == null || !authorization.startsWith("Bearer ")) {
                    filterChain.doFilter(request, response);
                    return;
                }
                authorization = authorization.substring(7);
                try {
                    Claims claims = jwtUtil.parseToken(authorization);
                    log.info("request user：{}", claims);
                    Integer userId = Integer.parseInt(claims.get("userId").toString());
                    Object redisUserInfo = redisTemplate.opsForValue().get(String.format(RedisConstant.LOGIN_USER, authorization, userId));
                    if (ObjectUtil.isNull(redisUserInfo)) {
                        throw new ServiceException("身份验证过期！");
                    }
                    RoccUnoUserDetails userDetails = JSONUtil.toBean(JSONUtil.toJsonStr(redisUserInfo), RoccUnoUserDetails.class);
                    SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()));
                } catch (Exception e) {
                    log.info("token验证失败：{}", e.getMessage());
                    handleAuthenticationEntryPoint(request, response, new ServiceException(SecurityConstant.AUTHENTICATION_FAIL_EXPIRE));
                    return;
                }

                //放行
                filterChain.doFilter(request, response);
            }
        };
    }

    public void handleAuthenticationEntryPoint(HttpServletRequest request, HttpServletResponse response, Exception exception) throws IOException {
        log.info("authentication{}错误：{}", exception.getClass().getName(), exception.getMessage());
        response.setContentType("application/json;charset=utf-8");
        PrintWriter w = response.getWriter();
        w.write(JSONUtil.toJsonStr(Result.fail(HttpServletResponse.SC_UNAUTHORIZED, exception.getMessage())));
        w.flush();
        w.close();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }
}
